/*
 * Copyright (c) 2016-2021, Hangzhou Qulian Technology Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "x509.h"

// ----------------------------------------------
// x509 type definition
// ----------------------------------------------
typedef ql_byte_t  x509_byte_t;
typedef ql_int32_t x509_size_t;
typedef ql_err_t   x509_err_t;
typedef ql_void_t  x509_void_t;

// ----------------------------------------------
// Base64 Decoder
// ----------------------------------------------
static const x509_byte_t base64_dec_map[128] = {
                127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
                127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
                127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
                127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
                127, 127, 127,  62, 127, 127, 127,  63,  52,  53,
                 54,  55,  56,  57,  58,  59,  60,  61, 127, 127,
                127,  64, 127, 127, 127,   0,   1,   2,   3,   4,
                  5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
                 15,  16,  17,  18,  19,  20,  21,  22,  23,  24,
                 25, 127, 127, 127, 127, 127, 127,  26,  27,  28,
                 29,  30,  31,  32,  33,  34,  35,  36,  37,  38,
                 39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
                 49,  50,  51, 127, 127, 127, 127, 127
                };
static const x509_byte_t base64_enc_map[64] = {
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
                'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
                'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
                'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
                'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
                '8', '9', '+', '/'
        };

// -- base64 decoding function --
static x509_err_t base64_decode(x509_byte_t* dst, x509_size_t dlen, x509_size_t *olen,
                         x509_byte_t* src, x509_size_t slen) {
    x509_size_t i, n, j, x;
    x509_byte_t *p;
    // First check for validity and get output length
    for(i = n = j = 0; i < slen; i++) {
        // Skip spaces before checking for EOL
        x = 0;
        while(i < slen && src[i] == ' '){ ++i; ++x; }
        // Spaces at end of buffer are OK
        if(i == slen) break;
        if((slen - i) >= 2 && src[i] == '\r' && src[i + 1] == '\n') continue;
        if(src[i] == '\n') continue;
        // Space inside a line is an error (INVALID_CHARACTER)
        if(x != 0)                                        return X509_ERR_BASE64_INVALID_CHAR;
        if(src[i] == '=' && ++j > 2)                      return X509_ERR_BASE64_INVALID_CHAR;
        if(src[i] > 127 || base64_dec_map[src[i]] == 127) return X509_ERR_BASE64_INVALID_CHAR;
        if(base64_dec_map[src[i]] < 64 && j != 0)         return X509_ERR_BASE64_INVALID_CHAR;
        n++;
    }
    if( n == 0 ) {
        *olen = 0;
        return X509_SUCCESS;
    }
    // The following expression is to calculate the following formula without risk of integer overflow in n
    n = (6 * ( n >> 3)) + ((6 * (n & 0x7) + 7) >> 3);
    n -= j;
    // BUFFER is too SMALL
    if(dst == NULL || dlen < n) {
        *olen = n;
        return X509_ERR_BASE64_BUF_TOO_SMALL;
    }
    // decode base64
    for(j = 3, n = x = 0, p = dst; i > 0; i--, src++) {
        if( *src == '\r' || *src == '\n' || *src == ' ' )
            continue;
        j -= (base64_dec_map[*src] == 64);
        x  = ( x << 6 ) | ( base64_dec_map[*src] & 0x3F );
        if(++n == 4) {
            n = 0;
            if( j > 0 ) *p++ = (unsigned char)( x >> 16 );
            if( j > 1 ) *p++ = (unsigned char)( x >>  8 );
            if( j > 2 ) *p++ = (unsigned char)( x       );
        }
    }
    *olen = p - dst;
    return X509_SUCCESS;
}

// -- base64 encoding function --
#define BASE64_SIZE_T_MAX 65535
static x509_err_t base64_encode(x509_byte_t* dst, x509_size_t dlen, x509_size_t *olen,
                  x509_byte_t* src, x509_size_t slen) {
    x509_size_t i, n, C1, C2, C3;
    x509_byte_t *p;

    if(slen == 0) {
        *olen = 0;
        return X509_SUCCESS;
    }
    n = slen / 3 + ( slen % 3 != 0 );
    if(n > ( BASE64_SIZE_T_MAX - 1 ) / 4) {
        *olen = BASE64_SIZE_T_MAX;
        return X509_ERR_BASE64_BUF_TOO_SMALL;
    }
    n *= 4;
    if(( dlen < n + 1 ) || ( NULL == dst )){
        *olen = n + 1;
        return X509_ERR_BASE64_BUF_TOO_SMALL;
    }
    n = (slen / 3) * 3;
    for(i = 0, p = dst; i < n; i += 3) {
        C1 = *src++;
        C2 = *src++;
        C3 = *src++;
        *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
        *p++ = base64_enc_map[(((C1 &  3) << 4) + (C2 >> 4)) & 0x3F];
        *p++ = base64_enc_map[(((C2 & 15) << 2) + (C3 >> 6)) & 0x3F];
        *p++ = base64_enc_map[C3 & 0x3F];
    }
    if(i < slen) {
        C1 = *src++;
        C2 = ( ( i + 1 ) < slen ) ? *src++ : 0;
        *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
        *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
        if( ( i + 1 ) < slen )
            *p++ = base64_enc_map[((C2 & 15) << 2) & 0x3F];
        else *p++ = '=';
        *p++ = '=';
    }
    *olen = p - dst;
    *p = 0;
    return X509_SUCCESS;
}

// ----------------------------------------------
// item Decoder
// ----------------------------------------------
// -- length info data struct --
typedef struct {
    x509_size_t len;     // data length
    x509_size_t lenLen;  // length of length
} x509_len_t;

// -- Analyze the data length according to the value, the process conforms to the asn.1 standard --
// -- data : Data array address --
// -- i    : offset             --
x509_len_t gel_len(x509_byte_t* data, x509_size_t i) {
    x509_size_t len_len, len, j;
    x509_len_t res;
    res.lenLen = 1;
    if (data[i] & 0x80) {
        len_len = data[i] & 0x7f;
        len = 0;
        i++;
        for (j = 0; j < len_len; j++) {
            len <<= 8;
            len += data[i + j];
        }
        res.len = len;
        res.lenLen += len_len;
    } else {
        res.len = data[i] & 0x7f;
    }
    return res;
}

// -- Init the item --
x509_void_t item_init(x509_item_t* it) {
    it->title = NULL;
    it->data  = NULL;
    it->type  = 0;
    it->len   = 0;
}

// -- Set the content of the item --
// -- NOTE: item can only be set once! --
x509_err_t item_set(x509_item_t* it, x509_byte_t* title, x509_size_t type, x509_byte_t* data, x509_size_t len)  {
    // set title
    x509_size_t t_len = strlen((char*)title);
    if(t_len > 0 && it->title == NULL){
        it->title = calloc(t_len+1, sizeof(x509_byte_t));
        memcpy(it->title, title, t_len);
    } else {
        return X509_ERR_PARSE_INVALID_PARAM;
    }
    // set data
    if(len > 0 && it->data == NULL){
        it->data = calloc(len, sizeof(x509_byte_t));
        memcpy(it->data, data, len);
    } else {
        return X509_ERR_PARSE_INVALID_PARAM;
    }
    // set type
    it->type = type;
    // set len
    it->len = len;
    return X509_SUCCESS;
}

// -- Free the memory occupied by the item --
x509_void_t item_free(x509_item_t* it) {
    if(it->title != NULL){
        free(it->title);
    }
    if(it->data != NULL){
        free(it->data);
    }
}

// -- Print hex type item --
x509_void_t item_print_hex(x509_item_t* it){
    x509_size_t i = 0;
    log_debug("[title]: %s \t[len  ]: %d", it->title, it->len);

    x509_byte_t buf[512];
    if(it->len < 250){
        for (i = 0; i < it->len; i++) {
            buf[i*2]   = ((it->data[i] & 0xf0) >> 4) <=9 ?  ((it->data[i] & 0xf0) >> 4) + '0' : ((it->data[i] & 0xf0) >> 4) + 'a' - 10;
            buf[i*2+1] = ((it->data[i] & 0x0f)     ) <=9 ?  ((it->data[i] & 0x0f)     ) + '0' : ((it->data[i] & 0x0f)     ) + 'a' - 10;
        }
        buf[i*2] = '\0';
        log_debug("[type ]: hex \t[data ]: %s", buf);
    } else {
        log_warn("[type ]: hex  \t[data ]: %s", "The length exceeds 250, so it cannot be printed.");
    }
}

// -- Print string type item --
x509_void_t item_print_str(x509_item_t* it) {
    log_debug("[title]: %s \t[len  ]: %d", it->title, it->len);
    log_debug("[type ]: hex  \t[data ]: %s", it->data);
}

// ----------------------------------------------
// key Decoder
// ----------------------------------------------
// -- Convert an integer number <value> into a string and store it in an array <result> --
// -- Returns the real length of the parsed data (Does not contain '\0') --
x509_size_t convert_itoa(x509_size_t value, x509_byte_t* result, x509_size_t base) {
    if (base < 2 || base > 36) { *result = '\0'; return 0; }
    x509_byte_t* ptr = result, *ptr1 = result, tmp_char;
    x509_size_t tmp_value;
    x509_size_t len = 0;
    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)];
    } while ( value );
    if (tmp_value < 0) *ptr++ = '-';
    *ptr-- = '\0';
    while(ptr1 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr1;
        *ptr1++ = tmp_char;
        len += 2;
    }
    if(ptr1 == ptr) len += 1;
    return len;
}

// -- String search substring --
// -- found => return index, not found => return -1, parameter error => returns -2 --
x509_size_t str_find_str(const x509_byte_t* src,const x509_byte_t* sub)
{
    x509_size_t index = 0;
    if(src == NULL || sub == NULL) {
        return -2;
    }
    while(*src) {
    	const x509_byte_t* bp;
    	const x509_byte_t* sp;
        bp = src;
        sp = sub;
        do {
            if(!*sp) return index;
        } while(*bp++ == *sp++);
        src += 1;
        index ++;
    }
    return -1;
}

// -- Parse Object ID --
// -- dst_data: Analysis result, ascii format --
// -- dst_len : Parsing length (contain '\0') --
// -- src_data: Original array, hex format    --
// -- src_len : Length of parse data needed   --
x509_void_t parse_oid(x509_byte_t* dst_data, x509_size_t* dst_len, x509_byte_t* src_data, x509_size_t src_len){

    x509_size_t oiFirst, oiStrFirst, itoa_len, t;
    // add first src_data
    (*dst_len) = 0;
    oiFirst    =  src_data[0] & 0x7f;
    oiStrFirst =  (oiFirst/40) < 2? (oiFirst/40) : 2;
    itoa_len   =  convert_itoa(oiStrFirst, &dst_data[(*dst_len)], 10);
    (*dst_len) += itoa_len;
    // add .
    dst_data[(*dst_len)] = '.';
    (*dst_len) ++;
    // add second data
    itoa_len   =  convert_itoa(oiFirst - 40 * oiStrFirst, &dst_data[(*dst_len)], 10);
    (*dst_len) += itoa_len;
    // add next data
    oiFirst = 0;
    for (t = 1; t < src_len; t++) {
        oiFirst <<= 7;
        oiFirst += src_data[t] & 0x7f;
        if (!(src_data[t] & 0x80)) {
            // add .
            dst_data[(*dst_len)] = '.';
            (*dst_len) ++;
            // add number
            itoa_len = convert_itoa(oiFirst, &dst_data[(*dst_len)], 10);
            (*dst_len) += itoa_len;
            oiFirst = 0;
        }
    }
    dst_data[(*dst_len)] = '\0';
    (*dst_len) ++;
}

// -- Decode string according to the syntax of ASN.1. Recursive method is used in the analysis process --
// -- [NOTE] The process can only parse the key string --
// -- key   : Object storing the key     --
// -- title : Identifies the current parsed level, the top level needs to be "" --
// -- data  : Original array, hex format --
// -- begin : start index (contain)      --
// -- end   : finish index (contain)     --
x509_err_t parse_x509(x509_key_t* key, x509_byte_t* title, x509_byte_t* data, x509_size_t begin, x509_size_t end) {

    x509_size_t i = begin;
    x509_size_t type;
    x509_len_t lens;
    x509_err_t it_res;

    while (i < end) {
        // T
        type = data[i++];
        // L
        lens = gel_len(data, i);
        if (i + lens.lenLen <= end) {
            i += lens.lenLen;
        }
        if (lens.len <0 || i + lens.len > end) {
            break;
        }
        // V
        switch (type) {
            // structure type
            case 0x30:  // sequence
                if(!strcmp((char*)title,"")){
                    parse_x509(key, (x509_void_t*)"top", data, i, i + lens.len);
                }
                break;
            case 0xa0:  // extern info
            case 0xa1:
                if(!strcmp((char*)title,"top")) {
                    parse_x509(key, (x509_void_t *) "extern", data, i, i + lens.len);
                }
                break;
            //Simple type
            case 0x02:
                if(!strcmp((char*)title,"top")) {
                    it_res = item_set(&key->version, (x509_void_t*)"version", type, &data[i], lens.len);
                    if(it_res != X509_SUCCESS) return it_res;
                }
                break;
            case 0x03:
                if(!strcmp((char*)title,"extern")) {
                    it_res = item_set(&key->public_key, (x509_void_t*)"pubkey", type, &data[i+1], lens.len-1 );
                    if(it_res != X509_SUCCESS) return it_res;
                }
                break;
            case 0x04:
                if(!strcmp((char*)title,"top")) {
                    it_res = item_set(&key->private_key, (x509_void_t*)"prikey", type, &data[i], lens.len );
                    if(it_res != X509_SUCCESS) return it_res;
                }
                break;
            case 0x06:
                if(!strcmp((char*)title,"extern")) {
                    x509_byte_t tmp[50] = {0};
                    x509_size_t length = 0;
                    parse_oid(tmp,&length,&data[i], lens.len);
                    it_res = item_set(&key->key_type, (x509_void_t*)"key_type", type, tmp, length);
                    if(it_res != X509_SUCCESS) return it_res;
                }
                break;
            default:
                break;
        }
        i += lens.len;
    }
    return X509_SUCCESS;
}

// -- Initialize the item object in the key --
x509_void_t keyInit(x509_key_t* key){
    item_init(&key->version);
    item_init(&key->public_key);
    item_init(&key->private_key);
    item_init(&key->key_type);
};

// -- Parse the key string --
// -- dst_key : Object storing the key       --
// -- src_data: Original data, string format --
x509_err_t keyParse(x509_key_t* dst_key, x509_byte_t* src_data){
    // Locate the location of the key string
    x509_byte_t* begin = (x509_byte_t*)"-----BEGIN EC PRIVATE KEY-----";
    x509_byte_t* end   = (x509_byte_t*)"-----END EC PRIVATE KEY-----";
    x509_size_t i, j;
    i = str_find_str(src_data, begin);
    if(i < 0)  {
        log_error("can not find begin symbol");
        return X509_ERR_PARSE_NO_FIND_SYMBAL;
    }
    j = str_find_str(src_data, end);
    if(j < 0)  {
        log_error("can not find end symbol");
        return X509_ERR_PARSE_NO_FIND_SYMBAL;
    }
    // Base64 decode
    x509_size_t  src_len = strlen((char*)begin);
    x509_byte_t  dst_buffer[130];
    x509_size_t  dst_len = 0;
    x509_size_t  res = base64_decode(dst_buffer, sizeof(dst_buffer), &dst_len, src_data+i+src_len, j-i-src_len);
    if(res != X509_SUCCESS) {
        log_error("base64 decode failed, error code : %d", res);
        return res;
    }
    log_debug("base64 decode success, [srcData len] = %d , [dstData len] = %d", j-i-src_len, dst_len);
    x509_byte_t buf[512];
    if(dst_len < 160){
        for (i = 0; i < dst_len; i++) {
            buf[i*3]   = ((dst_buffer[i] & 0xf0) >> 4) <=9 ?  ((dst_buffer[i] & 0xf0) >> 4) + '0' : ((dst_buffer[i] & 0xf0) >> 4) + 'a' - 10;
            buf[i*3+1] = ((dst_buffer[i] & 0x0f)     ) <=9 ?  ((dst_buffer[i] & 0x0f)     ) + '0' : ((dst_buffer[i] & 0x0f)     ) + 'a' - 10;
            buf[i*3+2] = ' ';
        }
        buf[i*3] = '\0';
        log_debug("[base64]: decoder = %s", buf);
    } else {
        log_warn("[base64]: %s", "The length exceeds 160, so it cannot be printed.");
    }
    // parse key
    res = parse_x509(dst_key, (x509_void_t*)"",dst_buffer,0,dst_len);
    if(res != X509_SUCCESS) {
        log_error("parse key failed, error code : %d", res);
        return res;
    }
    item_print_hex(&dst_key->version);
    item_print_hex(&dst_key->public_key);
    item_print_hex(&dst_key->private_key);
    item_print_str(&dst_key->key_type);
    // compare curve type
    if(strcmp((char*)dst_key->key_type.data,"1.3.132.0.10") != 0){
        log_error("key type is not secp256k1");
        return X509_ERR_PARSE_CURVE_MISMATCH;
    }
    return X509_SUCCESS;
}

// -- Free the memory occupied by the key --
x509_void_t keyFree(x509_key_t* key){
    item_free(&key->version);
    item_free(&key->public_key);
    item_free(&key->private_key);
    item_free(&key->key_type);
};
